﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using CubeDetection;

namespace CubeCalculations
{
   public class ThreadedEvolutionaryCubexSolutionNPlusAlpha
   {
      private readonly Cubex BaseCube;
      private readonly ThreadedEvolutionaryCubexWorker[] CubexWorker;
      private readonly Thread[] CubexWorkerThread;
      private Thread MainWorkerThread;
      private readonly Random random;
      private readonly Queue<List<int>> SolutionQueue = new Queue<List<int>>();
      private readonly AutoResetEvent ResetEvent = new AutoResetEvent(false);
      private const int ThreadCount = 2;
      private readonly MovementRating MovementRating = new MovementRating();

      public delegate void SetSolutionEventHandler(BacktrackingList Solution);

      public event StatusChangedHandler OnStatusChanged;

      private void InvokeOnStatusChanged(float Progress, float Fitness)
      {
         StatusChangedHandler Handler = OnStatusChanged;
         if (Handler != null) Handler(Progress, Fitness);
      }



      public event SetSolutionEventHandler OnSetSolution;

      private void InvokeOnSetSolution()
      {
         SetSolutionEventHandler Handler = OnSetSolution;
         if (Handler != null) Handler(Solutions);
      }

      public ThreadedEvolutionaryCubexSolutionNPlusAlpha()
      {
         BaseCube = new Cubex();
         random = new Random((int) DateTime.Now.Ticks);


         CubexWorker = new ThreadedEvolutionaryCubexWorker[ThreadCount];
         CubexWorkerThread = new Thread[ThreadCount];

         for (int i = 0; i < CubexWorker.Length; i++)
         {
            CubexWorker[i] = new ThreadedEvolutionaryCubexWorker();
            CubexWorker[i].OnRequestMoves += OnRequestMoves;
            CubexWorker[i].OnSetSolution += ThreadOnSetSolution;
            CubexWorkerThread[i] = new Thread(CubexWorker[i].EvaluateSolutions) {IsBackground = true};
            CubexWorkerThread[i].Start();
         }
      }

      public Cubex Cube
      {
         get { return BaseCube; }
         set
         {
            value.CopyTo(BaseCube);
         }
      }

      public List<CubeMoves> SolveCube()
      {
         BaseCube.SolveCube();
         return BaseCube.Solution();
      }


      private float BaseSolutionLength;
      private BacktrackingList Solutions;
      private BacktrackingList Probabilities;


      private void CalcProbabilities()
      {
         Probabilities = new BacktrackingList();
         float Sum = 0f;
         float BaseProb = 0f;
         foreach (var solution in Solutions)
         {
            Sum += solution.Key;
         }

         foreach (var solution in Solutions)
         {
            BaseProb += solution.Key/Sum;
            Probabilities.Add(BaseProb, new List<int>(solution.Value));
         }
      }
      private int SolutionsToCalc, SolutionsCalculated;

      private const int N = 15;
      private const int Alpha = 25;
      const int MaxGenerations = 20;
      //private const int N = 15;
      //private const int Alpha = 25;
      //const int MaxGenerations = 30;
      private void EvaluateSolution()
      {
         const int MinMoves = 1;
         const int MaxMoves = 7;

         SolutionsCalculated = 0;
         BaseSolutionLength = MovementRating.RateSolution(BaseCube);

         lock (this)
         {
            IsActive = true;
         }
         Solutions = new BacktrackingList();
         Solutions.Add(BaseSolutionLength,new List<int>());
         CalcProbabilities();

         SolutionsToCalc = Alpha * MaxGenerations;

         InvokeOnStatusChanged(0f, BaseSolutionLength);

         int Generations = 0;


         foreach (var worker in CubexWorker)
         {
            worker.Cube = BaseCube;
         }

         while (Generations++ < MaxGenerations && !interrupt)
         {
            float InitialProb = (float) random.NextDouble();
            const float Increment = 1f/(float) Alpha;
            float CurrentProb;
            

            for (int i = 0; i < Alpha; i++)
            {
               CurrentProb = InitialProb + Increment*(float) i;
               List<int> BaseList = new List<int>();

               foreach (var probability in Probabilities)
               {
                  if(probability.Key > CurrentProb)
                  {
                     BaseList = new List<int>(probability.Value);
                     break;
                  }
               }


               int MoveCount = random.Next(MinMoves, MaxMoves);
               for (int k = 0; k < MoveCount; k++)
               {
                  BaseList.Add(random.Next(0, BaseCube.CubeRotation.Length));
               }
               SolutionQueue.Enqueue(BaseList);
            }

            //Threads Starten
            ResedFinished();
            foreach (ThreadedEvolutionaryCubexWorker cubexWorker in CubexWorker)
            {
               cubexWorker.Continue();
            }

            //Auf beendigung warten
            ResetEvent.WaitOne();



            Solutions.RemoveAll(N);

            CalcProbabilities();
            InvokeOnStatusChanged((float)SolutionsCalculated / (float)SolutionsToCalc * 100f, Solutions.AtIndex(0).Key);
         }

         float ImprovementPercent = 100f - ((float)Solutions.AtIndex(0).Key / (float)BaseSolutionLength * 100f);

         InvokeOnSetSolution();
         lock (this)
         {
            IsActive = false;
         }
      }


      private bool interrupt = false;
      public void Interrupt()
      {
         lock (this)
         {
            interrupt = true;
            SolutionQueue.Clear();
         }
      }
      private bool IsActive = false;
      public bool EvaluateSolutions()
      {
         if (MainWorkerThread != null && MainWorkerThread.IsAlive)
            return false;

         interrupt = false;
         MainWorkerThread = new Thread(EvaluateSolution);
         MainWorkerThread.Start();
         ResetEvent.Set();
         return true;
      }

      private int FinishedThreads = 0;

      private void ThreadFinished()
      {
         lock (this)
         {
            FinishedThreads++;
            if (FinishedThreads == CubexWorker.Length)
               ResetEvent.Set();
         }
      }

      private void ResedFinished()
      {
         lock (this)
         {
            FinishedThreads = 0;
         }
      }

      private float BestSolutionLength = float.MaxValue;
      private int[] BestSolution;

      private void ThreadOnSetSolution(ThreadedEvolutionaryCubexWorker Sender, List<int> Solution, int SolutionLength, float KukaMoves)
      {
         lock (this)
         {
            SolutionsCalculated++;
            if (Solutions.ContainsKey(KukaMoves))
            {
               if (Solutions[KukaMoves].Count > Solution.Count)
               {
                  Solutions[KukaMoves].Clear();
                  Solutions[KukaMoves].AddRange(Solution);
               }
            }
            else
            {
               Solutions.Add(KukaMoves, new List<int>(Solution));
            }
         
            InvokeOnStatusChanged((float)SolutionsCalculated / (float)SolutionsToCalc * 100f, Solutions.AtIndex(0).Key);
            if (SolutionQueue.Count > 0)
               Sender.Continue();
            else
               ThreadFinished();
         }
      }

      private bool OnRequestMoves(ThreadedEvolutionaryCubexWorker Sender, out List<int> SolutionList)
      {
         SolutionList = null;
         lock (this)
         {
            if (SolutionQueue.Count > 0)
            {
               SolutionList = SolutionQueue.Dequeue();
               return true;
            }
            return false;
         }
      }

      
      private void PrepareCube(Cubex BaseCube, List<int> PreSolution)
      {
         foreach (var worker in CubexWorker)
         {
            worker.PrepareCube(BaseCube, PreSolution);
         }
      }
      private string PrepareCube(Cubex BaseCube, Cubex PreparedCube, List<int> PreSolution)
      {
         BaseCube.CopyTo(PreparedCube);
         return PreparedCube.Evaluate(PreSolution.ToArray());
      }

      private int SolveCube(Cubex Cube)
      {
         return SolveCube(Cube, 0);
      }

      private int SolveCube(Cubex Cube, int BaseSteps)
      {
         Cube.SolveCube();
         return Cube.Solution().Count + BaseSteps;
      }

      private int[] RandomMoves(int MinMoves, int MaxMoves, Cubex cubex)
      {
         int resCnt = random.Next(MinMoves, MaxMoves);
         var result = new int[resCnt];
         for (int i = 0; i < result.Length; i++)
         {
            result[i] = RandomMove(cubex);
         }
         return result;
      }

      private int RandomMove(Cubex cubex)
      {
         int result = random.Next(0, cubex.CubeRotation.Length);
         cubex.CubeRotation[result]();
         return result;
      }

      #region Nested type: BacktrackingList

      public class BacktrackingList : SortedList<float, List<int>>
      {
         public KeyValuePair<float, List<int>> AtIndex(int Index)
         {
            int Start = 0;
            foreach (var pair in this)
            {
               if (Start++ == Index)
                  return pair;
            }
            throw new IndexOutOfRangeException();
         }
         public float KeyAtIndex(int Index)
         {
            int Start = 0;
            foreach (var pair in this)
            {
               if (Start++ == Index)
                  return pair.Key;
            }
            throw new IndexOutOfRangeException();
         }
         public void RemoveAll(int StartIndex)
         {
            while (Count > StartIndex)
            {
               Remove(KeyAtIndex(Count - 1));
            }
         }
      }

      #endregion

      #region Nested type: BacktrackingPoint

      public class BacktrackingPoint
      {
         public float SolutionLength;
         public List<int> SolutionSteps = new List<int>();
         public BacktrackingList BackTrackingList = new BacktrackingList();
      }

      public class BTElement
      {
         public BTElement()
         {
            Childs = new BTChildList();
            Evaluated = false;
            Solution = new List<int>();
         }
         public BTElement(BTElement Parent): this()
         {
            this.Parent = Parent;
         }
         public BTElement Parent { get; set; }
         public BTChildList Childs { get; set; }
         public float Fitness { get; set; }
         public List<int> Solution { get; set; }
         public bool Evaluated { get; set; }
      }
      public class BTChildList : SortedList<float, BTElement>
      {
         public BTElement AtIndex(int Index)
         {
            int Start = 0;
            foreach (var pair in this)
            {
               if (Start++ == Index)
                  return pair.Value;
            }
            throw new IndexOutOfRangeException();
         }
         public float KeyAtIndex(int Index)
         {
            int Start = 0;
            foreach (var pair in this)
            {
               if (Start++ == Index)
                  return pair.Key;
            }
            throw new IndexOutOfRangeException();
         }
         public void RemoveAll(int StartIndex)
         {
            while (Count > StartIndex)
            {
               Remove(KeyAtIndex(Count - 1));
            }
         }
      }

      #endregion
   }
   
}